import { isUnaryTag } from "../../utils/utils.js"

/**
 * 解析模板字符串，生成AST
 * @param {*} template 
 * @returns {AST} ast语法树
 */
export function parse(template) {
  // 存放所有未配对的开始标签的 AST 对象
  const stack = []
  // 最终的AST语法树
  let root = null
  let html = template
  // 先去除空格
  while (html.trim()) {
    // 先去除对应的注释
    if (html.indexOf('<!--') === 0) {
      // 开始位置是注释, +3 是因为'-->'结束标记是3个字符
      html = html.slice(html.indexOf('-->') + 3)
      continue
    }

    // 开始匹配标签
    const startIdx = html.indexOf('<')
    if (startIdx === 0) {
      if (html.indexOf('</') === 0) {
        // 说明是闭合标签
        parseEnd()
      } else {
        parseStartTag()
      }
    } else if (startIdx > 0) {
      // 说明中间有文本：比如 文本</ span>，在html中找到下一个标签开始的位置
      const nextStartIdx = html.indexOf('<')
      // 如果栈为空，则说明这段文本不属于任何一个元素，直接丢弃
      if (stack.length) {
        processChars(html.slice(0, nextStartIdx))
      }
      // 把nextStartIdx开始往前的都截取掉
      html = html.slice(nextStartIdx)
    } else {

    }
  }
  /**
   * 开始一些处理方法
   */
  /**
 * 处理结束标签
 */
  function parseEnd() {
    // 找到结束标签位置
    html = html.slice(html.indexOf('>') + 1)
    // 处理栈顶元素
    processElement()
  }
  /**
   * 解析开始标签
   * 例如： <div id="app">xxxx</div>
   */
  function parseStartTag() {
    // 找到开始标签的结束位置
    const end = html.indexOf('>')
    // 解析内容： <内容>, 标签名 和 属性
    // 从1开始是去除开始标签 "<"
    const content = html.slice(1, end)
    // 截断html 将上面解析的内容从html字符串中删除
    html = html.slice(end + 1)
    // 找到第一个空格位置
    const firstSpaceIdx = content.indexOf(' ')
    // 标签名和属性字符串
    let tagName = '',
      attrStr = ''
    if (firstSpaceIdx === -1) {
      // 没有空格，则认为content是标签名, 比如<h3></h3>这种情况，content = h3
      tagName = content
      // 只有标签名，没有属性名
      attrStr = ''
    } else {
      // 获取标签名
      tagName = content.slice(0, firstSpaceIdx)
      // 剩下的内容就是属性了，你如id="app" class="content flex"
      attrStr = content.slice(firstSpaceIdx + 1)
    }
    // 得到属性数组
    const attrs = attrStr ? attrStr.split(' ') : []
    // 解析属性数组，得到一个Map对象
    const attrMap = parseAttrs(attrs)
    // 生成AST
    const elementAST = generateAST(tagName, attrMap)
    // console.log(elementAST)
    // 如果不存在根节点， 说明当前节点整个为模板的第一个节点
    if (!root) {
      root = elementAST
    }
    // 将ast对象push到栈中， 遇到结束标签时就讲栈顶的ast对象pop出来，拼成成对的完整标签
    stack.push(elementAST)
    // 自闭和标签，则直接调用end方法，不进入闭合处理，不入栈
    if (isUnaryTag(tagName)) {
      processElement()
    }
  }

  /**
   * 处理元素节点，闭合标签该方法
   * 进一步的处理元素的属性，将结果放到attr属性上
   */
  function processElement() {
    // 弹出栈顶元素
    const curEle = stack.pop()
    const stackLen = stack.length
    //  进一步处理AST中的rawAttr对象-> {attrName: attrValue, ...}
    const { tag, rawAttr } = curEle
    curEle.attr = {}
    // 获取所有的key
    const propertyArr = Object.keys(rawAttr)
    if (propertyArr.includes('v-model')) {
      // 处理v-mode
      processVModel(curEle)
    } else if (propertyArr.find(item => item.match(/^v-bind:(.*)/))) {
      // 处理v-bind
      processVBind(curEle, RegExp.$1, rawAttr[`v-bind:${RegExp.$1}`])
    } else if (propertyArr.find(item => item.match(/^v-on:(.*)/))) {
      // 处理v-on
      processVOn(curEle, RegExp.$1, rawAttr[`v-on:${RegExp.$1}`])
    }
    // 处理插槽
    processSlotContent(curEle)
    // 节点处理完毕后让父子节点产生关系，相互存着对方
    if (stackLen) {
      stack[stackLen - 1].children.push(curEle)
      curEle.parent = stack[stackLen - 1]
      // 如果节点存在slotName, 说明该节点是组件传递给插槽的内容
      // 将插槽信息放到组件节点的rawAttr.scopedSlots对象上
      // 而这些信息在生成组件插槽的VNode时（renderSlot）会用到
      if (curEle.slotName) {
        const { parent, slotName, scopeSlot, children } = curEle
        const slotInfo = {
          slotName,
          scopeSlot,
          children: children.map(item => {
            delete item.parent
            return item
          })
        }

        if (parent.rawAttr.scopedSlots) {
          parent.rawAttr.scopedSlots[curEle.slotName] = slotInfo
        } else {
          parent.rawAttr.scopedSlots = { [curEle.slotName]: slotInfo }
        }
      }
    }
  }
  /**
 * 处理文本
 * @param {*} text 
 */
  function processChars(text) {
    // 去除左右空格
    if (!text.trim()) return
    // 构建文本节点的AST对象
    const textAst = {
      type: 3,
      text
    }
    if (text.match(/{{(.*)}}/)) {
      // 匹配到{{}} 说明是表达式
      textAst.expression = RegExp.$1.trim()
    }
    // 将ast 放到栈顶元素内部
    stack.at(-1).children.push(textAst)
  }
  return root
}




/**
 * 解析属性数组，得到一个属性和值组成的Map对象
 */
function parseAttrs(attrs) {
  const attrMap = {}
  for (let i = 0; i < attrs.length; i++) {
    const attr = attrs[i]
    // 提取属性的key和value
    // class="flex"
    const [attrName, attrValue] = attr.split('=')
    // 去除属性值两边的引号
    attrMap[attrName] = attrValue.replace(/"/g, '')
  }
  return attrMap
}
/**
 * 生成AST对象
 * @param {*} tagName 标签名
 * @param {*} attrMap 标签属性 map 对象
 */
function generateAST(tagName, attrMap) {
  return {
    // 元素节点
    type: 1,
    // 标签名
    tag: tagName,
    // 属性 map 对象
    rawAttr: attrMap,
    // 子节点
    children: []
  }
}


/**
 * 处理v-model指令，将结果放到curEle 对象上
 * @param {*} curEle 
 */
function processVModel(curEle) {
  const { tag, rawAttr, attr } = curEle
  const { type, 'v-model': vModelVal } = rawAttr
  
  if (tag === 'input') {
    if (/text/.test(type)) {
      // type = text的情况
      attr.vModel = {
        tag,
        type: 'text',
        value: vModelVal
      }
    } else if (/checkbox/.test('checkbox')) {
      attr.vModel = {
        tag,
        type: 'checkbox',
        value: vModelVal
      }
    }
  } else if (tag === 'textarea') {
    // <textarea v-model="inputVal" />
    attr.vModel = {
      tag,
      value: vModelVal
    }
  } else if (tag === 'select') {
    // <select v-mode="inputVal"></select>
    attr.vModel = {
      tag,
      value: vModelVal
    }
  }
}

/**
 * 处理v-bind
 * @param {*} curEle 当前的AST对象
 * @param {*} bindKey v-bind的key
 * @param {*} bindVal v-bind的value
 */
function processVBind(curEle, bindKey, bindVal) {
  curEle.attr.vBind = {
    [bindKey]: bindVal
  }
}

/**
 * 处理v-on
 * @param {*} curEle 当前的AST对象
 * @param {*} vOnKey v-on的key
 * @param {*} vOnVal v-on的value
 */
function processVOn(curEle, vOnKey, vOnVal) {
  curEle.attr.vOn = {
    [vOnKey]: vOnVal
  }
}
/**
 * 处理插槽
 * @param {*} el 
 */
function processSlotContent(el) {
  if (el.tag === 'template') {
    // 属性map对象
    const attrMap = el.rawAttr
    for (let key in attrMap) {
      if (key.match(/v-slot:(.*)/)) {
        // 说明template标签上有v-slot
        const slotName = el.slotName = RegExp.$1
        el.scopeSlot = attrMap[`v-slot:${slotName}`]
        return
      }
    }
  }
}